eletrotupi / tcc/ frontend/app/sleep/[id].tsxbb0d5b0826e3b53aa1560d3f17c08f2afb52c272
7.4 KB | 296 lines Raw
1
import { useState, useEffect } from "react";
2
import {
3
  StyleSheet,
4
  View,
5
  ScrollView,
6
  Pressable,
7
  ActivityIndicator,
8
} from "react-native";
9
import { Text } from "@/components/ui/Text";
10
import { useLocalSearchParams, router, Stack } from "expo-router";
11

12
import { Card, SubtleInfoCard } from "@/components/ui/Cards";
13
import { Button } from "@/components/ui/Button";
14
import { Col, Row } from "@/components/ui/LayoutHelpers";
15
import { Section, SectionHeader } from "@/components/ui/Sections";
16
import { Theme, Colors, Spacing } from "@/constants/theme";
17
import { MaterialIcons } from "@expo/vector-icons";
18
import {
19
  useSleepRecord,
20
  useDeleteSleepRecord,
21
} from "@/hooks/useSleepRecord.queries";
22
import { formatDate, parseDateOnly } from "@/lib/utils/time";
23

24
export default function SleepDetailScreen() {
25
  const { id } = useLocalSearchParams<{ id: string }>();
26
  const { data: sleepRecord, isLoading, isError } = useSleepRecord(id);
27
  const deleteSleepRecord = useDeleteSleepRecord();
28
  const [minutesLeft, setMinutesLeft] = useState(0);
29
  const [canDelete, setCanDelete] = useState(false);
30

31
  useEffect(() => {
32
    const updateTimer = () => {
33
      const createdAt = sleepRecord?.createdAt;
34
      if (!createdAt) return;
35

36
      const msSinceCreation = Date.now() - new Date(createdAt).getTime();
37
      const deletionWindowMs = 5 * 60 * 1000;
38

39
      const newCanDelete = msSinceCreation < deletionWindowMs;
40
      const newMinutesLeft = Math.max(
41
        0,
42
        Math.ceil((deletionWindowMs - msSinceCreation) / 60000),
43
      );
44

45
      setCanDelete(newCanDelete);
46
      setMinutesLeft(newMinutesLeft);
47
    };
48

49
    if (isLoading) return;
50

51
    updateTimer(); // Set initial values
52

53
    const interval = setInterval(updateTimer, 1000);
54

55
    return () => clearInterval(interval);
56
  }, [sleepRecord, isLoading]);
57

58
  if (isLoading) {
59
    return (
60
      <View style={styles.centered}>
61
        <ActivityIndicator size="large" color={Colors.light.tint} />
62
      </View>
63
    );
64
  }
65

66
  if (isError || !sleepRecord) {
67
    return (
68
      <View style={styles.centered}>
69
        <Text style={styles.errorText}>Registro não encontrado.</Text>
70
        <Pressable onPress={() => router.back()} style={styles.backButton}>
71
          <Text style={styles.backButtonText}>Voltar</Text>
72
        </Pressable>
73
      </View>
74
    );
75
  }
76

77
  const handleDelete = async () => {
78
    await deleteSleepRecord.mutateAsync(String(sleepRecord.id));
79

80
    router.back();
81
  };
82

83
  const deleteButtonMsg = () => {
84
    const msg = "Excluir Registro";
85
    const timeLeftStr = `(Disp. por ${minutesLeft < 1 ? "<1" : minutesLeft}:00)`;
86
    const suffix = canDelete ? timeLeftStr : "(Expirado)";
87

88
    return `${msg} ${suffix}`;
89
  };
90

91
  return (
92
    <>
93
      <Stack.Screen options={{ title: "Ver detalhes" }} />
94

95
      <ScrollView
96
        style={styles.container}
97
        contentContainerStyle={styles.content}
98
        showsVerticalScrollIndicator={false}
99
      >
100
        {/* Header card */}
101
        <Card style={styles.headerCard}>
102
          <Row style={{ alignItems: "center" }}>
103
            <View style={[styles.iconWrap, { backgroundColor: '#ede9fe' }]}>
104
              <MaterialIcons name="bedtime" size={22} color="#7c3aed" />
105
            </View>
106
            <Col>
107
              <Row style={{ alignItems: "flex-end" }}>
108
                <Text style={styles.averageLabel}>{sleepRecord.average}h</Text>
109
                <Text style={styles.suffixLabel}>de sono</Text>
110
              </Row>
111

112
              <Text style={styles.dateLabel}>
113
                {formatDate(parseDateOnly(sleepRecord.date))}
114
              </Text>
115
            </Col>
116
          </Row>
117
        </Card>
118

119
        {/* Annotation / notes */}
120
        {sleepRecord.annotations && (
121
          <Section>
122
            <SectionHeader title="Anotações" variant="subtle" />
123
            <Card style={{ padding: Spacing.cardGap }}>
124
              <Text style={styles.annotation}>
125
                &quot;{sleepRecord.annotations?.trim()}&quot;
126
              </Text>
127
            </Card>
128
          </Section>
129
        )}
130

131
        {/* Delete */}
132
        <View style={styles.section}>
133
          <Text style={styles.sectionLabel}>GESTÃO DO REGISTRO</Text>
134
          <Button
135
            variant="outline"
136
            loading={false}
137
            disabled={!canDelete}
138
            onPress={() => router.push(`/sleep/edit/${sleepRecord.id}`)}
139
            title="Editar Registro"
140
          />
141

142
          <Button
143
            variant="danger"
144
            loading={deleteSleepRecord.isPending}
145
            disabled={!canDelete || deleteSleepRecord.isPending}
146
            onPress={handleDelete}
147
            title={deleteButtonMsg()}
148
          />
149

150
          <SubtleInfoCard
151
            style={styles.deleteInfo}
152
            text={`
153
              A exclusão de registros é permitida apenas nos primeiros 5 minutos
154
              após o envio para garantir a integridade do histórico emocional.
155
              `}
156
          />
157
        </View>
158
      </ScrollView>
159
    </>
160
  );
161
}
162

163
const styles = StyleSheet.create({
164
  container: {
165
    flex: 1,
166
    backgroundColor: Theme.colors.light.background,
167
  },
168
  content: {
169
    padding: Spacing.containerPadding,
170
    gap: Spacing.sectionGap,
171
    paddingBottom: 48,
172
  },
173
  iconWrap: {
174
    width: 40,
175
    height: 40,
176
    borderRadius: 20,
177
    alignItems: 'center',
178
    justifyContent: 'center',
179
    marginTop: 1
180
  },
181
  headerCardContent: {
182
    flexDirection: "row",
183
    alignItems: "center",
184
    gap: 8,
185
  },
186
  averageLabel: {
187
    height: '100%',
188
    fontSize: 24,
189
    fontWeight: "700",
190
    color: Colors.light.text,
191
  },
192
  suffixLabel: {
193
    height: '100%',
194
    fontSize: 17,
195
    fontWeight: "700",
196
    color: Colors.light.textSecondary,
197
  },
198
  dateLabel: {
199
    fontSize: 13,
200
    color: Colors.light.textSecondary,
201
  },
202
  centered: {
203
    flex: 1,
204
    justifyContent: "center",
205
    alignItems: "center",
206
    gap: 12,
207
  },
208
  errorText: {
209
    fontSize: 15,
210
    color: Colors.light.textSecondary,
211
  },
212
  backButton: {
213
    paddingHorizontal: 20,
214
    paddingVertical: 10,
215
    backgroundColor: Theme.colors.light.tint,
216
    borderRadius: Theme.borderRadius.md,
217
  },
218
  backButtonText: {
219
    fontWeight: "700",
220
    color: Theme.colors.light.text,
221
  },
222

223
  // Header
224
  headerCard: {
225
    padding: 16,
226
  },
227
  moodIcon: {
228
    fontSize: 40,
229
  },
230
  moodLabel: {
231
    fontSize: 17,
232
    fontWeight: "700",
233
    color: Colors.light.text,
234
  },
235
  moodTime: {
236
    fontSize: 13,
237
    color: Colors.light.textSecondary,
238
  },
239

240
  // Sections
241
  section: {
242
    gap: 8,
243
  },
244
  sectionLabel: {
245
    fontSize: 11,
246
    fontWeight: "600",
247
    letterSpacing: 0.8,
248
    color: Colors.light.textSecondary,
249
    textTransform: "uppercase",
250
  },
251

252
  // Components
253
  componentRow: {
254
    flexDirection: "row",
255
    justifyContent: "space-between",
256
    alignItems: "center",
257
    paddingVertical: 10,
258
  },
259
  componentLabel: {
260
    fontSize: 15,
261
    fontWeight: "700",
262
    color: Colors.light.text,
263
  },
264
  componentIntensity: {
265
    fontSize: 13,
266
    color: Colors.light.textSecondary,
267
  },
268
  componentIntensityGreen: {
269
    fontSize: 13,
270
    fontWeight: "700",
271
    color: Theme.colors.light.tint,
272
  },
273
  progressTrack: {
274
    height: 6,
275
    backgroundColor: Theme.colors.light.divider,
276
    borderRadius: Theme.borderRadius.full,
277
    marginBottom: 4,
278
    overflow: "hidden",
279
  },
280
  progressFill: {
281
    height: "100%",
282
    backgroundColor: Theme.colors.light.tint,
283
    borderRadius: Theme.borderRadius.full,
284
  },
285
  // Annotation
286
  annotation: {
287
    fontSize: 14,
288
    lineHeight: 22,
289
    color: Colors.light.textSecondary,
290
    fontStyle: "italic",
291
    padding: 4,
292
  },
293
  deleteInfo: {
294
    marginTop: 4,
295
  },
296
});